package sagroup.dracots;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Logger;

import lime2.Location;
import lime2.TupleID;

import sagroup.dracots.commands.*;
import sagroup.dracots.policy.RPTuple;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

/**
 * This class is ReactionListener for CommandReaction.
 * 
 * @author Huang Xiaodong
 */

public class CommandReactionListener extends DracoReactionListener {
	/**
	 * current tuple space that use this reactionListener
	 */
	private DracoTS dts;

	/**
	 * Constructor.
	 * 
	 * @param lts
	 *            current tuple space that use this reaction listener
	 */
	public CommandReactionListener(DracoTS dts) {
		this.dts = dts;
	}
	
	private Logger LOG = Logger.getLogger(this.getClass().getName());

	/**
	 * This method is invoked automatically by Lime when the corresponding
	 * reaction must be fire.
	 * 
	 * @param re
	 *            the ReactionEvent that triggered the reaction
	 * @author Huang Xiaodong
	 */
	@Override
	public void reactsToWrite(DracoReactionEvent re) {
		SignalTuple cmd;
		
		cmd = (SignalTuple)re.getTuple();
		ReplicaRequestRegistrar registrar = dts.getReplicaRequestRegistrar();
		if (cmd instanceof AddReplicaRequestByTypeCommand) {
			handleAddReplicaRequest((AddReplicaRequestByTypeCommand)cmd, registrar);
			
		} else if (cmd instanceof AddReplicaRequestByEntryCommand) {
			handleAddReplicaRequest((AddReplicaRequestByEntryCommand)cmd, registrar);
			
		} else if (cmd instanceof RemoveReplicaRequestCommand) {
			handleRemoveReplicaRequest((RemoveReplicaRequestCommand)cmd, registrar);
			
		} else if (cmd instanceof UpdateReplicaCommand) {
			handleUpdateReplica((UpdateReplicaCommand)cmd, registrar);
			
		} else if (cmd instanceof PushCommand) {
			handlePush((PushCommand)cmd);
			
		} else if (cmd instanceof ClearReplicaCommand) {
			handleClearReplica((ClearReplicaCommand)cmd, registrar);
			
		}
	}

	/**
	 * @param cmd
	 * @param registrar
	 * @author Xing Jiankuan
	 */
	private void handleClearReplica(ClearReplicaCommand clearCmd, ReplicaRequestRegistrar registrar) {
		TupleID[] rmvVIDs = clearCmd.getVIDs();
		if (rmvVIDs != null) {
			PostingData recurVIDs = new PostingData();
			// remove local replicas
			dts.lrr.removeReplicas(rmvVIDs);

			for (int i = 0; i < rmvVIDs.length; ++i) {

				// for recursive update, PostingData helps to
				// put removed VersionLayerIDs with the same destination
				// together
				Location[] wheretos = registrar
						.deregisterReplica(rmvVIDs[i]);
				if (wheretos != null)
					for (int j = 0; j < wheretos.length; ++j)
						recurVIDs.putData(wheretos[j], rmvVIDs[i]);
			}
			// TODO: Here need to check whether the policy is ANY. if(policy
			// == PDPolicy.ANY) do recursive clear below.
			Location[] dests = recurVIDs.getAllDest();
			if (dests != null)
				for (int i = 0; i < dests.length; ++i) {
					Object[] obj_vids = recurVIDs.getData(dests[i]);
					if (obj_vids != null) {
						TupleID[] tid_vids = new TupleID[obj_vids.length];
						for (int j = 0; j < obj_vids.length; ++j)
							tid_vids[j] = (TupleID) obj_vids[j];
						dts.out(new ClearReplicaCommand(tid_vids, dests[i]
										));
					}
				}
		}
	}

	/**
	 * @param cmd
	 * @author Xing Jiankuan
	 */
	private void handlePush(PushCommand pushCmd) {
		ShareTuple[] replicas = pushCmd.getTuples();
		// add local replicas
		for (int i = 0; i < replicas.length; i++) {
			replicas[i].setCurr(Location.HERE); 
		}
		
		// try to perform the policy switch
		for (ShareTuple r : replicas) {
			if (r instanceof RPTuple) {	
				RPTuple rp = (RPTuple)r;
				dts.switcher.switchPolicy(rp.getTargetType(), rp.getTargetNode(), rp.getPolicy());
			}
		}
		dts.lrr.addReplicas(replicas);
	}

	/**
	 * @param cmd
	 * @param registrar
	 * @author Xing Jiankuan
	 */
	private void handleUpdateReplica(UpdateReplicaCommand updateCmd,
			ReplicaRequestRegistrar registrar) {
		
		ShareTuple[] newTuples = updateCmd.getTuples();
		PostingData recurTuples = new PostingData();
		if (newTuples != null)
			for (int i = 0; i < newTuples.length; ++i) {
				// read local old tuple
				ShareTuple vidTemplate = ShareTuple
						.createVIDTemplate(newTuples[i].getVID());
				ShareTuple oldTuple = (ShareTuple) dts.lrr.rrdp(vidTemplate);
				
				
				// if local version is older than incoming version, replace
				// it.
				if(oldTuple == null)
					continue;
				if (oldTuple.getVer() < newTuples[i].getVer()) {
					newTuples[i].setCurr(Location.HERE); // the current location must be reset
					newTuples[i].setDest(Location.HERE); // the destination location also must be reset HERE
					
					if (newTuples[i] instanceof RPTuple) {
						RPTuple rp = (RPTuple)newTuples[i];
						dts.switcher.switchPolicy(rp.getTargetType(), rp.getTargetNode(), rp.getPolicy());
					}
					
					dts.lrr.rupdate(oldTuple, newTuples[i]);
					
					// for recursive update, PostingData helps to
					// put duplicated tuples with the same destination
					// together
					Location[] wheretos = registrar.updateReplica(
							newTuples[i].getVID(), newTuples[i].getVer());
					if (wheretos != null)
						for (int j = 0; j < wheretos.length; ++j)
							recurTuples.putData(wheretos[j], newTuples[i].duplicate(wheretos[j]));
				}
			}
		// TODO: Here need to check whether the policy is ANY.
		// if(policy == PDPolicy.ANY) do recursive update below.
		Location[] dests = recurTuples.getAllDest();
		if (dests != null)
			for (int i = 0; i < dests.length; ++i)
				dts.out(new UpdateReplicaCommand((ShareTuple[]) recurTuples.getData(dests[i]),
						dests[i]));
	}

	/**
	 * @param cmd
	 * @param registrar
	 * @author Xing Jiankuan
	 */
	private void handleRemoveReplicaRequest(RemoveReplicaRequestCommand removeCmd,
			ReplicaRequestRegistrar registrar) {
		
		TupleID[] vids = removeCmd.getVIDs();
		Location dest = removeCmd.getSrc();

		// de-register the replication
		if (vids != null)
			for (int i = 0; i < vids.length; ++i)
				registrar.deregisterReplica(vids[i], dest);
	}

	/**
	 * @param cmd
	 * @param registrar
	 * @author Xing Jiankuan
	 */
	private void handleAddReplicaRequest(AddReplicaRequestByEntryCommand addCmd,
			ReplicaRequestRegistrar registrar) {
		
		Location dest = addCmd.getSrc();
		AddReplicaRequestByEntryCommand.ReplicaRequestEntry[] entries = addCmd
				.getEntries();
		ArrayList<ShareTuple> pushList = new ArrayList();
		ArrayList<ShareTuple> updateList = new ArrayList();
		ArrayList<TupleID> clearList = new ArrayList();
		if (entries != null)
			for (int i = 0; i < entries.length; ++i)
				if (registrar.getReplicaVersion(entries[i].vid, dest) == null) {
					// collect and replicate tuples
					
					ShareTuple master = (ShareTuple) dts.rdp(ShareTuple
									.createVIDTemplate(entries[i].vid));
					if(master==null)
					{
						LOG.warning("Err, The tuple " + entries[i].vid + " in this host maybe already deleted!!");
						clearList.add(entries[i].vid);
						continue;
					}
//					else
//						LOG.info("The tuple " + entries[i].vid + " has been built replica connection successfully!!");
					
					ShareTuple replica = master.replicate(dest);

					if (replica != null) {
						// If replication does not exist at requesting host,
						// use "push"
						if (entries[i].ev == DracoConstants.INVALID_VER_NUM)
							pushList.add(replica);

						// If replication at requesting host is older, use
						// "update"
						else if (entries[i].ev < replica.getVer())
							updateList.add(replica);

						// register the replication
						registrar.registerReplica(replica.getVID(), dest,
								replica.getVer());
					}
				}

		// Change ArrayLists into Arrays
		ShareTuple[] pushReplicas = new ShareTuple[pushList.size()];
		pushReplicas = pushList.toArray(pushReplicas);
		ShareTuple[] updateReplicas = new ShareTuple[updateList.size()];
		updateReplicas = updateList.toArray(updateReplicas);
		TupleID[] clearReplicas = new TupleID[clearList.size()];
		clearReplicas = clearList.toArray(clearReplicas);
		// Push and update the replications.
		if (pushReplicas.length > 0)
			dts.out(new PushCommand(pushReplicas, dest));
		if (updateReplicas.length > 0)
			dts.out(new UpdateReplicaCommand(updateReplicas, dest));
		// delete those replicas whose master has been deleted already
		if(clearReplicas.length > 0)
			dts.out(new ClearReplicaCommand(clearReplicas,dest));
	}
	
	private void handleAddReplicaRequest(AddReplicaRequestByTypeCommand addCmd,
			ReplicaRequestRegistrar registrar) {
		throw new NotImplementedException();
		//TODO: implement handleAddReplicaRequest for ByType command
	}

	/**
	 * This class is used to collect data that is used when doing recursive
	 * operation in CommandReaction. For example, VIDs when doing recursive
	 * clear, or duplicated share-tuples when doing recursive update. The tuples
	 * are organized in a HashMap. The HashMap is in the form of
	 * HashMap<Location,ArrayList>. It helps to get the objects that should be
	 * sent to the same destination.
	 * 
	 * @author Huang Xiaodong
	 */
	public static class PostingData {
		/**
		 * The HashMap to organize the data
		 */
		private HashMap<Location, ArrayList> dataMap;

		/**
		 * Constructor.
		 */
		public PostingData() {
			dataMap = new HashMap<Location, ArrayList>();
		}

		/**
		 * Put data with a specified destination into the HashMap.
		 * 
		 * @param dest
		 *            specified destination
		 * @param data
		 *            data to put
		 * @author Huang Xiaodong
		 */
		public void putData(Location dest, Object data) {
			ArrayList list = dataMap.get(dest);
			if (list == null) {
				list = new ArrayList();
				dataMap.put(dest, list);
			}
			list.add(data);
		}

		/**
		 * Get all data with the specified destination.
		 * 
		 * @param dest
		 *            specified destination
		 * @return all data with the specified destination
		 * @author Huang Xiaodong
		 */
		public Object[] getData(Location dest) {
			ArrayList list = dataMap.get(dest);
			return list.toArray();
		}

		/**
		 * Get all destinations in the HashMap
		 * 
		 * @return all destinations in the HashMap
		 * @author Huang Xiaodong
		 */
		public Location[] getAllDest() {
			if (dataMap.keySet() != null) {
				Object[] obj_dests = dataMap.keySet().toArray();
				Location[] loc_dests = new Location[obj_dests.length];
				for (int i = 0; i < obj_dests.length; ++i)
					loc_dests[i] = (Location) obj_dests[i];
				return loc_dests;
			} else
				return null;
		}

	}
}
